Skytoby

Android Framework接口开发总结

Android Framework接口开发总结

Android Framwork开发过程中,经常会遇到新增接口的需求,下面将介绍基本上所有需要用到的接口开发流程。

一、JNI接口

在系统开发接口过程中,很多情况需要通过JNI接口的方式调用一些库或者驱动,通过Android studio的方式构建native工程比较简单,下面主要介绍在framework层如何自定义jni接口。

JNI的原理参考:https://skytoby.github.io/2020/Android%20JNI%20%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/

1.1 源码编译so库

在 vendor 目录下创建 skytoby目录,我们把代码放到 testjni 里面。

测试jni接口源码文件,里面有一个jni方法还有一个带回调的jni方法例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <jni.h>
#include <utils/Log.h>

/**
* sayHello方法的实现
* @param jniEnv JNINativeInterface_结构体的二级指针对象里面包含了很多函数指针
* @param jobject1 调用此方法的对象实例(即main方法中的dynamicDemo)
* @return
*/
jstring jniSayHello(JNIEnv *jniEnv, jobject){
return jniEnv->NewStringUTF("我是Native方法sayHello的实现你好");
}
/**
* sayBye方法的实现
* @param jniEnv JNINativeInterface_结构体的二级指针对象里面包含了很多函数指针
* @param jobject1 调用此方法的对象实例(即main方法中的dynamicDemo)
* @return
*/
jstring jniSayBye(JNIEnv *jniEnv, jobject){
return jniEnv->NewStringUTF("我是Native方法sayBye的实现拜拜");
}

void jniRegisterCallback(JNIEnv* jniEnv, jobject, jobject jcallback) {
jclass callbackClass = jniEnv->GetObjectClass(jcallback);
jmethodID callbackMethodId = jniEnv->GetMethodID(callbackClass, "sayCallback", "(Ljava/lang/String;)V");
jniEnv->CallVoidMethod(jcallback, callbackMethodId, jniEnv->NewStringUTF("我是jniRegisterCallback回调"));
}

/**
* 声明sayHello和sayBye的类路径
*/
static const char* mClassName="com/skytoby/JniTest";

/**
* JNINativeMethod结构体
* 包含:name-方法名;signature-方法签名(描述返回值和入参);fnPtr-c中实现的函数指针
* 方法签名可以根据javap命令反编译查看或使用jclasslib插件查看
*/
static const JNINativeMethod mMethod[]={
{"sayHello","()Ljava/lang/String;",reinterpret_cast<jstring *>(jniSayHello)},
{"sayBye","()Ljava/lang/String;",reinterpret_cast<jstring *>(jniSayBye)},
{"registerCallBack","(Lcom/skytoby/HelloCallback;)V",reinterpret_cast<void*>(jniRegisterCallback)}
};

int registerNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
jclass clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGV("registerNativeMethods 1");
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGV("registerNativeMethods 2");
return JNI_FALSE;
}
return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM* jvm, void*) {
JNIEnv *env = NULL;
if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
ALOGV("JNI_OnLoad 1");
return JNI_ERR;
}
if (registerNativeMethods(env, mClassName, mMethod, sizeof(mMethod) / sizeof(mMethod[0])) < 0) {
ALOGV("JNI_OnLoad 2");
return JNI_ERR;
}
return JNI_VERSION_1_6;
}

testjni目录下增加编译文件文件Android.bp

1
2
3
4
5
6
7
cc_library_shared {
name: "libtestjni",

srcs: [
"JniTest.cpp",
],
}

在testjni目录下下,执行mm,执行成功后会在对应的out目前下的system\lib64有一个libtestjni.so文件生成,说明编译成功,那如何让Java层的Framework可以调用到这个库里面的方法,下面将介绍。

1.2 so库导入系统

将库编译进系统,只需要在device目前下的mk文件加上如下代码即可

1
PRODUCT_PACKAGES += libtestjni

1.3 Java jni调用

加载so,编写native函数

1
2
3
4
5
6
7
8
9
10
11
package com.skytoby;

public class JniTest {
static {
System.loadLibrary("testjni");
}

public native String sayHello();
public native String sayBye();
public native void registerCallBack(HelloCallback callback);
}

回调接口

1
2
3
4
5
package com.skytoby;

public interface HelloCallback {
void sayCallback(String msg);
}

测试代码

1
2
3
4
5
6
7
8
9
JniTest jniTest = new JniTest();
Log.d("skytoby",jniTest.sayHello());
Log.d("skytoby",jniTest.sayBye());
jniTest.registerCallBack(new HelloCallback() {
@Override
public void sayCallback(String msg) {
Log.d("skytoby","sayCallback " + msg);
}
});

在framework代码中加上测试代码,可以正常看到打印。

二、Jave服务接口

2.1 使用 aidl 定义服务接口

首先我们得定义我们的服务名是什么,提供什么样的接口。 在这个例子里面,我们的服务就叫 HelloService,它提供了下接口:

  • void hello(String name) //播放指定路径的音频文件

在 frameworks/base/core/java/android 目录下创建 pure 目录,我们把代码放到 pure 里面。 pure 目录下增加中文件 IHelloService.aidl

1
2
3
4
package android.pure;
interface IHelloService {
void hello(String name);
}

在 frameworks/base/Android.bp framework-defaults 模块中添加我们刚刚加的 aidl 文件

1
"core/java/android/pure/IHelloService.aidl",

然后进入 framework/base 目录执行 mm -j 命令编译 framework.jar 模块。 编译成功后,会在 out/soong/.intermediates/frameworks/base/framework/android_common/gen/aidl/frameworks/base/core/java/android/pure 目录生成 IHelloService.java 这个文件。 这个文件封装了 binder 通信的相关细节,有兴趣的同学可以去了解一下。

2.2 实现接口

在 frameworks/base/services/core/java/com/android/server 目录下新建 HelloService.java 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.android.server;

import android.pure.IHelloService;
import android.util.Log;

public class HelloService extends IHelloService.Stub {
private final String TAG = "HelloService";

public HelloService() {
Log.d(TAG, "create hello service");
}

@Override
public void hello(String name) {
Log.d(TAG, "hello " + name);
}
}

2.3 将服务添加到 ServiceManager

修改 frameworks/base/services/java/com/android/server/SystemServer.java 文件,在 startOtherServices 方法里面增加以下代码:

1
2
3
4
// add hello service
traceBeginAndSlog("HelloService");
ServiceManager.addService("HelloService", new HelloService());
traceEnd();

至此服务算是添加完了,我们来编译运行一下,发现系统起不来了,看下错误 log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
12-20 23:46:47.308 3521 3521 I SystemServer: HelloService
12-20 23:46:47.308 3521 3521 D HelloService: create hello service
12-20 23:46:47.308 1526 1526 E SELinux : avc: denied { add } for service=HelloService pid=3521 uid=1000 scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0
12-20 23:46:47.308 1526 1526 E ServiceManager: add_service('HelloService',95) uid=1000 - PERMISSION DENIED
12-20 23:46:47.308 3521 3521 E System : ******************************************
12-20 23:46:47.308 3521 3521 E System : ************ Failure starting system services
12-20 23:46:47.308 3521 3521 E System : java.lang.SecurityException
12-20 23:46:47.308 3521 3521 E System : at android.os.BinderProxy.transactNative(Native Method)
12-20 23:46:47.308 3521 3521 E System : at android.os.BinderProxy.transact(BinderProxy.java:510)
12-20 23:46:47.308 3521 3521 E System : at android.os.ServiceManagerProxy.addService(ServiceManagerNative.java:156)
12-20 23:46:47.308 3521 3521 E System : at android.os.ServiceManager.addService(ServiceManager.java:192)
12-20 23:46:47.308 3521 3521 E System : at android.os.ServiceManager.addService(ServiceManager.java:161)
12-20 23:46:47.308 3521 3521 E System : at com.android.server.SystemServer.startOtherServices(SystemServer.java:1914)
12-20 23:46:47.308 3521 3521 E System : at com.android.server.SystemServer.run(SystemServer.java:512)
12-20 23:46:47.308 3521 3521 E System : at com.android.server.SystemServer.main(SystemServer.java:349)
12-20 23:46:47.308 3521 3521 E System : at java.lang.reflect.Method.invoke(Native Method)
12-20 23:46:47.308 3521 3521 E System : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
12-20 23:46:47.308 3521 3521 E System : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:908)
12-20 23:46:47.308 3521 3521 D SystemServerTiming: HelloService took to complete: 1ms

SELinux : avc: denied { add } for service=HelloService 这个信息来看,是因为我们没有添加 SELinux 规则,什么是 SELinux 这里就不展开了,同学们可以自己去查下资料。

2.4 selinux 规则设置

Android 10 的 selinux 规则是放在 system/sepolicy 目录下的。 但 Android 每个版本 selinux 的添加规则多多少少是有些变化的,我们要怎么把这个新服务的 SELinux 规则给加上呢, 我们可以参考现有的系统服务的规则去添加,这里参考的是 network_time_update_service 服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd system/sepolicy
grep -nr network_time_update_service

...
prebuilts/api/28.0/plat_pub_versioned.cil:2650:(type network_time_update_service)
prebuilts/api/28.0/plat_pub_versioned.cil:2651:(typeattribute network_time_update_service_28_0)
prebuilts/api/28.0/plat_pub_versioned.cil:2652:(roletype object_r network_time_update_service_28_0)
prebuilts/api/28.0/public/service.te:107:type network_time_update_service, system_server_service, service_manager_type;
prebuilts/api/28.0/private/compat/27.0/27.0.cil:385:(expandtypeattribute (network_time_update_service_27_0) true)
prebuilts/api/28.0/private/compat/27.0/27.0.cil:1102:(typeattributeset network_time_update_service_27_0 (network_time_update_service))
prebuilts/api/28.0/private/compat/26.0/26.0.cil:389:(typeattributeset network_time_update_service_26_0 (network_time_update_service))
prebuilts/api/28.0/private/service_contexts:109:network_time_update_service u:object_r:network_time_update_service:s0
public/service.te:125:type network_time_update_service, system_server_service, service_manager_type;
private/compat/27.0/27.0.cil:391:(expandtypeattribute (network_time_update_service_27_0) true)
private/compat/27.0/27.0.cil:1108:(typeattributeset network_time_update_service_27_0 (network_time_update_service))
private/compat/26.0/26.0.cil:395:(typeattributeset network_time_update_service_26_0 (network_time_update_service))
private/compat/28.0/28.0.cil:463:(expandtypeattribute (network_time_update_service_28_0) true)
private/compat/28.0/28.0.cil:1306:(typeattributeset network_time_update_service_28_0 (network_time_update_service))
private/service_contexts:132:network_time_update_service u:object_r:network_time_update_service:s0

涉及到的文件很多,有部分文件是不需要修改的,我们先把找到的所有 service.te 和 service_contexts 都参考 network_time_update_service 加上 HelloService 的配置。

service_contexts 加上

1
HelloService u:object_r:HelloService:s0

service.te 加上

1
type HelloService, system_server_service, service_manager_type;

2.5 编译验证

完成以上各步骤的修改后,我们就可以先来验证一下,服务是否添加成功了。 先 make api-stubs-docs-update-current-api -j20 更新一下 api 接口,再整编系统。 运行虚拟机,执行 service list 看看有没有 HelloService 服务。

1
2
3
pure:/ # service list | grep HelloService 
16 HelloService: [android.pure.IHelloService]
pure:/ #

2.6 客户端调用

我们创建完服务端之后,还要考虑怎么提供 api 接口给到 client 使用。我们分为两种情况来讨论。

2.6.1 系统源码中使用

假如我们上面添加的服务是给在系统源码中编译的模块使用的,那就简单了。比如一个 apk ,我们在系统源码中创建一个 apk 模块。目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models$ tree PureSettings/
PureSettings/
├── Android.bp
├── AndroidManifest.xml
├── res
│ ├── layout
│ │ └── activity_main.xml
│ └── mipmap-hdpi
│ └── ic_launcher.png
└── src
└── com
└── pure
└── settings
└── MainActivity.java

7 directories, 5 files
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models$

Android.bp:

1
2
3
4
5
6
7
android_app {
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
name: "PureSettings",
certificate: "platform",
platform_apis: true,
}

其中 platform_apis 要设置为 true,不然没法调用我们新加的服务和 ServiceManager 。

AndroidManifest.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pure.settings">
<application
android:icon="@mipmap/ic_launcher"
android:label="PureSettings"
android:supportsRtl="true">
<activity android:name="com.pure.settings.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

res/layout/activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test" />
</LinearLayout>

src/com/pure/settings/MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.pure.settings;

import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.pure.IHelloService;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

private static final String TAG = "PureSettings";
private IHelloService service = null;
private Button button;

private void test() {
Log.d(TAG, "test");
try {
service.hello("skytoby");
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
service = IHelloService.Stub.asInterface(ServiceManager.getService("HelloService"));

button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
test();
}
});
}
}

把模块添加到系统, pure.mk:

1
PRODUCT_PACKAGES += PureSettings

编译运行,闪退了,看下错误日志,发现:

1
12-21 23:55:32.970 1526 1526 E SELinux : avc: denied { find } for service=HelloService pid=4266 uid=10102 scontext=u:r:platform_app:s0:c512,c768 tcontext=u:object_r:HelloService:s0 tclass=service_manager permissive=0

看起来是还有 selinux 权限需要添加。具体分析如下 缺少什么权限:{find}权限, 谁缺少权限:scontext=u:r:platform_app:s0:c512,c768 对哪个文件缺少权限:tcontext=u:object_r:HelloService:s0 什么类型的文件:tclass=service_manager

完整的意思: platform_app 缺少 service_manager 类型的 HelloService 的 find 权限。

根据以上分析,我们就可以找到解决方案: 在 platform_app.te 中添加 HelloService 的 find 权限即可。

1
allow platform_app HelloService:service_manager find;allow platform_app HelloService:service_manager find;

我们查找一下有哪些 platform_app.te 文件:

1
2
3
4
5
6
7
8
9
10
11
skytoby@skytoby-pc:~/source/android-10/system/sepolicy$ find -name platform_app.te 
./prebuilts/api/27.0/public/platform_app.te
./prebuilts/api/27.0/private/platform_app.te
./prebuilts/api/29.0/public/platform_app.te
./prebuilts/api/29.0/private/platform_app.te
./prebuilts/api/26.0/public/platform_app.te
./prebuilts/api/26.0/private/platform_app.te
./prebuilts/api/28.0/public/platform_app.te
./prebuilts/api/28.0/private/platform_app.te
./public/platform_app.te
./private/platform_app.te

把找到的所有 private/platform_app.te 都加上后,再重新编译运行发现不会挂了,看下 logcat:

1
2
3
130|pure:/ # logcat -c;logcat -s "HelloService:V" "PureSettings:V" 
12-22 10:47:26.505 4291 4291 D PureSettings: test
12-22 10:47:26.506 1757 3514 D HelloService: hello skytoby

的确调用到了我们添加的服务。

2.6.2 非系统源码中使用

前面实现了在 Android 系统源码中调用新添加服务的方法,但在实际项目中很多情况是在非系统源码环境下使用的。这种情况我们一般是再封装一层接口出来。 调用的层级为: apk –> HelloApi –> HelloService 我们按以下目录结构创建 HelloApi 模块:

1
2
3
4
5
6
7
8
9
10
11
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models$ tree HelloApi/
HelloApi/
├── Android.bp
└── java
└── com
└── pure
└── api
└── HelloManager.java

4 directories, 2 files
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models$

Android.bp:

1
2
3
4
5
6
7
java_library {
name: "com.pure.api",
installable: true,
srcs: [
"java/**/*.java", //文件列表
],
}

HelloManager.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.pure.api;

import android.os.RemoteException;
import android.os.ServiceManager;
import android.pure.IHelloService;

public class HelloManager {

private static HelloManager mInstance = null;
public static HelloManager getInstance() {
if (null == mInstance) {
mInstance = new HelloManager();
}
return mInstance;
}

private IHelloService mService = null;
private HelloManager() {
mService = IHelloService.Stub.asInterface(ServiceManager.getService("HelloService"));
}

public void sayHello(String name) {
try {
mService.hello(name);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

然后到 HelloApi 目录 mm 编译模块,得到 out/target/common/obj/JAVA_LIBRARIES/com.pure.api_intermediates/classes.jar 文件。 我们把这个 classes.jar 文件,复制到用 android-studio 创建的 apk 项目 的 app/libs 目录下,改名为 com.pure.api.jar 。 右键单击 com.pure.api.jar 选择 add as library, 然后我们就可以在 apk 的代码中使用 com.pure.api.jar 的接口了:

1
2
3
4
5
6
7
8
9
...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HelloManager.getInstance().sayHello("skytoby");
}
}

安装 apk 到虚拟机上运行,又遇到了 SELinux : avc: denied 错误,这次的错误是

1
E SELinux : avc:  denied  { find } for service=HelloService pid=4266 uid=10102 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:HelloService:s0 tclass=service_manager permissive=0

跟上面的错误基本上是一样的,只不过是把 platform_app 换成了 untrusted_app 而已。platform_app 表示有系统签名的 apk, untrusted_app 表示没有系统签名的 apk。 我们按同样的思路添加完 selinux 的权限后,重新编译运行虚拟机,就可以正常调用服务的接口了。

2.7 添加服务回调

2.7.1 使用 aidl 定义callback接口

在 frameworks/base/core/java/android/pure 目录下创建 ICallback.aidl

1
2
3
4
package android.pure;
interface ICallback {
void onMessage(in String message);
}

在 frameworks/base/Android.bp framework-defaults 模块中添加我们刚刚加的 aidl 文件

1
"core/java/android/pure/ICallback.aidl",

然后进入 framework/base 目录执行 mm -j 命令编译 framework.jar 模块。 编译成功后,会在 out/soong/.intermediates/frameworks/base/framework/android_common/gen/aidl/frameworks/base/core/java/android/pure 目录生成 ICallback.java 这个文件。

2.7.2 添加注册回调接口

修改 IHelloService.aidl 如下

1
2
3
4
5
6
7
package android.pure;
import android.pure.ICallback;
interface IHelloService {
void hello(in String name);
void registerCallback(in int pid, in ICallback callback);
void unregisterCallback(in int pid);
}

HelloService.java 修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.android.server;

import android.os.RemoteException;
import android.pure.IHelloService;
import android.pure.ICallback;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

public class HelloService extends IHelloService.Stub {
private final String TAG = "HelloService";
private Map<Integer, ICallback> mClients;

public HelloService() {
Log.d(TAG, "create hello service");
mClients = new HashMap<>();
}

@Override
public void hello(String name) {
Log.d(TAG, "hello " + name);
try {
for (ICallback callback : mClients.values()) {
callback.onMessage("message from service");
}
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void registerCallback(int pid, ICallback callback) {
mClients.put(pid, callback);
Log.d(TAG, "registerCallback client's size = " + mClients.size());
}

@Override
public void unregisterCallback(int pid) {
mClients.remove(pid);
Log.d(TAG, "unregisterCallback client's size = " + mClients.size());
}
}

我们把 client 传递过来的 callback 保存到 mClients 集合中,当 client 调用 hello 接口时, 遍历 mclients 把消息传递给 client。

2.7.3 client 注册回调接口

修改 PureSettings apk 中的 MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.pure.settings;

import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.pure.ICallback;
import android.pure.IHelloService;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

private static final String TAG = "PureSettings";
private IHelloService service = null;

private Button button;

private ICallback callback = new ICallback.Stub() {

@Override
public void onMessage(String message) throws RemoteException {
Log.d(TAG, "onMessage:" + message);
}
};

private void test() {
Log.d(TAG, "test");
try {
service.hello("skytoby");
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
service = IHelloService.Stub.asInterface(ServiceManager.getService("HelloService"));
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
test();
}
});
}

@Override
protected void onResume() {
super.onResume();
try {
service.registerCallback(android.os.Process.myPid(), callback);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
protected void onStop() {
super.onStop();
try {
service.unregisterCallback(android.os.Process.myPid());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

2.7.4 编译验证

整编系统,运行虚拟机,打开 PureSettings 测试,查看 logcat

1
2
3
4
5
6
7
pure:/ # logcat -s HelloService:D PureSettings:D
02-02 22:57:37.913 1769 1769 D HelloService: create hello service
02-02 22:57:53.498 1769 3058 D HelloService: registerCallback client's size = 1
02-02 22:57:56.587 3580 3580 D PureSettings: test
02-02 22:57:56.587 1769 3058 D HelloService: hello skytoby
02-02 22:57:56.588 3580 3580 D PureSettings: onMessage:message from service
02-02 22:58:01.160 1769 3503 D HelloService: unregisterCallback client's size = 0

从 log 上看,client 收到了 service 传递过来的消息。

三、Native服务接口

上一小节我们学习了 java 层的系统服务,但有时候出于性能方面的考虑,有些服务是需要使用 c++ 来实现的,比如音视频编解码,图形绘制等。 Android 系统原生的 MediaPlayerService 和 SurfaceFlinger 就是使用 c++ 实现的 Native 系统服务。这一小节我们就来学习一下如何添加一个 HelloNativeService。

3.1 声明服务接口

我们先在 $device 目录下创建 HelloNativeService 目录

1
2
cd device/skytoby/pure
mkdir -p models/HelloNativeService

然后创建 service 的头文件 HelloNativeService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef ANDROID_10_HELLONATIVESERVICE_H
#define ANDROID_10_HELLONATIVESERVICE_H

#include <binder/IInterface.h>
#include <binder/Parcel.h>
#include <utils/Errors.h>
using namespace android;
class HelloNativeService: public BBinder {
public:
HelloNativeService();
static int instantiate();
virtual status_t onTransact(uint32_t, const Parcel&, Parcel*, uint32_t);

};
#endif //ANDROID_10_HELLONATIVESERVICE_H

这里我们直接继承的是 BBinder, 而不是像其他教程一样又 BpInterface, 又 BnInterface 的,还把 BpInterface 跟 BnInterface 的实现放到同一个 cpp 文件中, 把 client 跟 server 的代码放到同一个文件中,个人感觉把简单的事情复杂化了。

3.2. 实现服务功能

创建文件 HelloNativeService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <android/log.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include "HelloNativeService.h"

#define LOG_TAG "HelloNativeService"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

enum {
CMD_SAY_HELLO = 1,
CMD_CAL_SUM = 2
};

static void sayHello(const char *name) {
LOGD("hello %s from HelloNativeService", name);
}

static int sum(int a, int b) {
return a + b;
}

HelloNativeService::HelloNativeService() {
LOGD("HelloNativeService created");
}

int HelloNativeService::instantiate() {
int r = defaultServiceManager()->addService(String16("HelloNativeService"),new HelloNativeService());
LOGD("add HelloNativeService r = %d", r);
return r;
}

status_t HelloNativeService::onTransact(uint32_t code, const Parcel &request, Parcel *reply, uint32_t flag) {
switch (code) {
case CMD_SAY_HELLO:
sayHello(request.readCString());
return NO_ERROR;

case CMD_CAL_SUM:
int a = request.readInt32();
int b = request.readInt32();
reply->writeInt32(sum(a, b));
return NO_ERROR;
}
return BBinder::onTransact(code, request, reply, flag);
}

其中最重要的是 onTransact 函数的实现,这是一个回调函数,当 client 端通过 binder 调用 server 功能的时候, server 端的 onTransact 回调函数就会被调用。 各参数理解如下:

  • code : 表示要执行的动作,类似Handler发送的Message的what。code指示了当前远程操作的命令,IBinder定义了像INTERFACE_TRANSACTION、PING_TRANSACTION 这样的几个通用命令。自己使用的命令的标识值需要在FIRST_CALL_TRANSACTION和LAST_CALL_TRANSACTION之间。
  • request, reply : request 和 reply 参数相当于普通函数里的调用参数和返回值。Parcel类型是可以跨进程的数据。
  • flag : 参数 flags 只有 0 和 FLAG_ONEWAY 两种。0 表示阻塞调用, FLAG_ONEWAY 表示异步调用。这个参数不需要我们在服务端处理, Binder 框架会自动处理。

3.3. 添加 main.cpp

前面我们定义了服务接口而已,这个服务也是需要依赖在某个进程才能运行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include "HelloNativeService.h"

using namespace android;

int main(int argc, char *argv[]) {
sp<ProcessState> proc(ProcessState::self());

HelloNativeService::instantiate();

ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
return 0;
}

以上都是样板代码,具体 ProcessState 的功能作用是什么这里就不展开了。

3.4 Android.bp

1
2
3
4
5
6
7
8
9
10
11
cc_binary {
name: "HelloNativeService",
srcs: ["main.cpp", "HelloNativeService.cpp"],

shared_libs: [
"liblog",
"libcutils",
"libutils",
"libbinder",
],
}

3.5 selinux 规则设置

为了简单起见,我们直接修改系统默认的 sepolicy 规则路径了。

3.5.1 添加 HelloNativeService.te

system/sepolicy/private/HelloNativeService.te 和 system/sepolicy/prebuilts/api/29.0/private/HelloNativeService.te 文件内容保持一致

1
2
3
4
5
6
7
type HelloNativeService, domain;
typeattribute HelloNativeService coredomain;
type HelloNativeService_exec, system_file_type, exec_type, file_type;
binder_use(HelloNativeService)
init_daemon_domain(HelloNativeService)

allow HelloNativeService HelloNativeService_service:service_manager { add find };

3.5.2 修改 file_contexts

system/sepolicy/private/file_contexts 和 system/sepolicy/prebuilts/api/29.0/private/file_contexts 文件内容保持一致,添加以下配置

1
/system/bin/HelloNativeService u:object_r:HelloNativeService_exec:s0

3.5.3 修改 service_contexts

system/sepolicy/private/service_contexts 和 system/sepolicy/prebuilts/api/29.0/private/service_contexts 文件内容保持一致,添加以下配置

1
HelloNativeService u:object_r:HelloNativeService_service:s0

5.4 修改 service.te

system/sepolicy/private/service.te 和 system/sepolicy/prebuilts/api/29.0/private/service.te,添加以下配置

1
type HelloNativeService_service,    service_manager_type;

3.6 开机自动运行

在 device/skytoby/pure 目录添加rc文件 init.ranchu.rc

1
2
3
4
5
service HelloNativeService /system/bin/HelloNativeService
class core
user root
group root
seclabel u:r:HelloNativeService:s0

在 pure.mk 中添加以下配置, 这个配置需要添加在 $(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_x86_64.mk) 之前。

1
PRODUCT_COPY_FILES += device/skytoby/pure/init.ranchu.rc:root/init.ranchu.rc

3.7 编写 client 调用服务

我们把测试代码跟 service 放在同一个目录,直接在 HelloNativeService 目录下创建 HelloNativeTest.cpp 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
#include <utils/Errors.h>
#include <stdio.h>

using namespace android;

enum {
CMD_SAY_HELLO = 1,
CMD_CAL_SUM = 2
};

static sp<IBinder> service;

static void test_sayHello() {
Parcel request, reply;
request.writeCString("skytoby");
service->transact(CMD_SAY_HELLO, request, &reply);
}

static void test_calSum() {
Parcel request, reply;
request.writeInt32(2);
request.writeInt32(3);
service->transact(CMD_CAL_SUM, request, &reply);
int sum = reply.readInt32();
printf("sum of 2 + 3 = %d\n", sum);
}

int main () {
service = defaultServiceManager()->getService(String16("HelloNativeService"));
test_sayHello();
test_calSum();
return 0;
}

Android.bp 中添加

1
2
3
4
5
6
7
8
9
10
11
cc_binary {
name: "HelloNativeTest",
srcs: ["HelloNativeTest.cpp"],

shared_libs: [
"liblog",
"libcutils",
"libutils",
"libbinder",
],
}

3.8 编译运行

经过以上步骤,我们的 native 服务和测试 demo 就完成了,目录结构如下:

1
2
3
4
5
6
7
8
9
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models$ tree HelloNativeService/
HelloNativeService/
├── Android.bp
├── HelloNativeService.cpp
├── HelloNativeService.h
├── HelloNativeTest.cpp
└── main.cpp

0 directories, 5 files

我们还需要把刚刚添加的这两个模块添加到 PRODUCT_PACKAGES

1
PRODUCT_PACKAGES += HelloNativeService HelloNativeTest

然后编译运行,查看服务列表,可以看到我们的 HelloNativeService 已经在运行状态了。 然后执行 HelloNativeTest,服务端正确返回了计算结果。 查看一下 logcat, HelloNativeService 也正确收到了 HelloNativeTest 传递过来的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
skytoby@skytoby-pc:~/source/android-10$ adb shell
pure:/ # service list | grep -i hello
15 HelloService: [android.pure.IHelloService]
161 HelloNativeService: []
pure:/ #
pure:/ # HelloNativeTest
sum of 2 + 3 = 5
pure:/ #
pure:/ # logcat -s HelloNativeService
--------- beginning of main
--------- beginning of system
01-05 00:03:19.788 1661 1661 D HelloNativeService: HelloNativeService created
01-05 00:03:19.788 1661 1661 D HelloNativeService: add HelloNativeService r = 0/n
01-05 00:05:50.011 1661 1670 D HelloNativeService: hello skytoby from HelloNativeService

3.9 添加native服务回调

3.9.1 client 端更改

为了简单省事,我们直接在上一小节的 demo 基础上作修改, HelloNatvieTest.cpp 修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
#include <utils/Errors.h>
#include <stdio.h>

using namespace android;

enum {
CMD_SAY_HELLO = 1,
CMD_CAL_SUM = 2,
CMD_CONNECT = 3
};

class Callback :public BBinder {
public:
enum {
CALLBACK_SAY_HELLO = 1,
};
status_t onTransact(uint32_t code, const Parcel &request, Parcel *reply, uint32_t flag) {
printf("Callback onTransact\n");
switch (code) {
case CALLBACK_SAY_HELLO:
const char* str = request.readCString();
printf("msg from server:%s\n", str);
break;
}

return BBinder::onTransact(code, request, reply, flag);
}

};

static sp<IBinder> service;

static void test_sayHello() {
Parcel request, reply;
request.writeCString("skytoby");
service->transact(CMD_SAY_HELLO, request, &reply);
}

static void test_calSum() {
Parcel request, reply;
request.writeInt32(2);
request.writeInt32(3);
service->transact(CMD_CAL_SUM, request, &reply);
int sum = reply.readInt32();
printf("sum of 2 + 3 = %d\n", sum);
}

static void test_connect(sp<Callback> callback) {
Parcel request, reply;
request.writeStrongBinder(callback);
service->transact(CMD_CONNECT, request, &reply);
}

int main () {
service = defaultServiceManager()->getService(String16("HelloNativeService"));
sp<Callback> callback = new Callback();
test_connect(callback);
test_sayHello();
test_calSum();
sleep(10);
return 0;
}

增加了一个 Callback 类,并且增加了一个 CMD_CONNECT 命令。 main 函数中创建了一个 Callback 对象,然后通过 writeStrongBinder 把这个对象传给 server。

3.9.2 server 端更改

HelloNativeService.h ,增加client变量修改如下:

1
sp<IBinder> client;

HelloNativeService.cpp 修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <android/log.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include "HelloNativeService.h"

#define LOG_TAG "HelloNativeService"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

enum {
CMD_SAY_HELLO = 1,
CMD_CAL_SUM = 2,
CMD_CONNECT = 3
};

enum {
CALLBACK_SAY_HELLO = 1,
};

static void sayHello(const char *name) {
LOGD("hello %s from HelloNativeService", name);
}

static void echo(sp<IBinder> client, const char *name) {
Parcel request, reply;
char buff[1024];
memset(buff, 0, sizeof(buff));
sprintf(buff, "hello %s from HelloNativeService", name);
request.writeCString(buff);
client->transact(CALLBACK_SAY_HELLO, request, &reply);
}

static int sum(int a, int b) {
return a + b;
}

HelloNativeService::HelloNativeService() {
LOGD("HelloNativeService created");
}

int HelloNativeService::instantiate() {
int r = defaultServiceManager()->addService(String16("HelloNativeService"),
new HelloNativeService());
LOGD("add HelloNativeService r = %d", r);
return r;
}

status_t
HelloNativeService::onTransact(uint32_t code, const Parcel &request, Parcel *reply, uint32_t flag) {
LOGD("service onTransact code = %d", code);
switch (code) {
case CMD_SAY_HELLO: {
const char *name = request.readCString();
sayHello(name);
echo(client, name);
return NO_ERROR;
}

case CMD_CAL_SUM: {
int a = request.readInt32();
int b = request.readInt32();
reply->writeInt32(sum(a, b));
return NO_ERROR;
}

case CMD_CONNECT: {
client = request.readStrongBinder();
return NO_ERROR;
}
}
return BBinder::onTransact(code, request, reply, flag);

}

增加了一个 CMD_CONNECT 命令,通过 readStrongBinder 把 client 传送过来的 callback 对象给读取保存下来。后面需要回调客户端的时候只需要通过 client 这个 binder 就可以。 client binder 与 server binder 的一个差异是 client binder 对象不需要添加到系统服务里面去,而是通过 writeStrongBinder 直接传送给 server。

3.9.3 编译验证

1
2
3
4
5
6
7
8
9
10
11
12
pure:/ # HelloNativeTest                      
Callback onTransact
msg from server:hello skytoby from HelloNativeService
sum of 2 + 3 = 5
pure:/ # logcat -s HelloNativeService
--------- beginning of system
--------- beginning of main
01-12 17:07:34.250 1645 1660 D HelloNativeService: service onTransact code = 3
01-12 17:07:34.252 1645 1660 D HelloNativeService: service onTransact code = 1
01-12 17:07:34.252 1645 1660 D HelloNativeService: hello skytoby from HelloNativeService
01-12 17:07:34.252 1645 1660 D HelloNativeService: service onTransact code = 2
130|pure:/ #

四、HIDL服务接口

Android 8.0 之后,google 为了解决 Android 版本碎片化问题, 推出了 treble 架构,参考 Android Treble架构解析。核心思想就是 vendor 分区和 system 分区的隔离。把厂商的更改限制在 vendor 分区,system 分区由 google 把控。system 分区需要访问 vendor 分区的话,需要通过 hidl 的形式来访问。 网上有很多介绍 hidl 的文章,但没有一篇是介绍得比较全面的,都会遗漏一些小细节,由于这样,那样的小细节,自己到处找资料,折腾了两天才把 hidl 的demo 跑起来。以下作详细的记录,手把手教你从零创建自己的 hidl 服务。关于什么是 hidl ,及为什么要用 hidl 的理论知识这里就不作介绍了,可以参考官网的介绍 : hidl概览

4.1 添加 hal 目录

网上的 demo 全部都是直接在 android/hardware/interfaces 目录下添加了, 但我觉得最好是在自己的 device 目录下添加比较好维护一点。

1
mkdir -p device/skytoby/pure/models/hidl/tvserver/1.0

4.2 定义 hal 接口

在 device/skytoby/pure/models/hidl/tvserver/1.0 目录下创建文件 ITVServer.hal

1
2
3
4
5
package device.skytoby.pure.tvserver@1.0;

interface ITVServer {
hello(string name) generates (string result);
};

4.3 根据 .hal 自动生成 cpp 实现

1
2
3
skytoby@skytoby-pc:~/source/android-10$ PACKAGE=device.skytoby.pure.tvserver@1.0
skytoby@skytoby-pc:~/source/android-10$ LOC=device/skytoby/pure/models/hidl/tvserver/1.0/default
skytoby@skytoby-pc:~/source/android-10$ hidl-gen -o $LOC -Lc++-impl -rdevice.skytoby.pure:device/skytoby/pure/models/hidl $PACKAGE

执行完以上命令之后在 default 目录下生成了 TVServer.h 和 TVServer.cpp 这两个文件 我们需要对 TVServer.cpp 实现进行完善,只需要对我们定义的 hello 接口进行实现就行了,修改如下:

1
2
3
4
5
6
7
8
Return<void> TVServer::hello(const hidl_string& name, hello_cb _hidl_cb) {
char buf[100];
::memset(buf, 0, 100);
::snprintf(buf, 100, "hello %s from TVServer", name.c_str());
hidl_string result(buf);
_hidl_cb(result);
return Void();
}

我们还需要一个进程来容纳我们的服务,创建一个 main.cpp 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <hidl/HidlTransportSupport.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>
#include "TVServer.h"
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using device::skytoby::pure::tvserver::V1_0::ITVServer;
using device::skytoby::pure::tvserver::V1_0::implementation::TVServer;

int main(int argc, char **argv) {
// sleep for a second, wait /data/vendor to be mounted
sleep(1);

configureRpcThreadpool(4, true /* callerWillJoin */);

android::sp<ITVServer> service = new TVServer();
android::status_t ret = service->registerAsService();

if (ret != android::NO_ERROR) {
}

joinRpcThreadpool();

return 0;
}

4.4 开机自动启动服务

在 tvserver/1.0/default 目录下创建 device.skytoby.pure.tvserver@1.0-service.rc 文件:

1
2
3
4
service tvserver /vendor/bin/hw/device.skytoby.pure.tvserver@1.0-service
class hal
user root
group root

4.5 生成 Android.bp

1
hidl-gen -o $LOC -Landroidbp-impl -rdevice.skytoby.pure:device/skytoby/pure/models/hidl $PACKAGE

在 Android 9.0 之后,查看自动生成的 Android.bp 文件,里面有些提示,说明自动生成的文件还需要稍作修改,我们修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cc_binary {
name: "device.skytoby.pure.tvserver@1.0-service",
init_rc: ["device.skytoby.pure.tvserver@1.0-service.rc"],
relative_install_path: "hw",
vendor: true,
srcs: [
"TVServer.cpp",
"main.cpp",
],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"device.skytoby.pure.tvserver@1.0",
],
}

其中 device.skytoby.pure.tvserver@1.0-service.rc 文件会被安装到 /vendor/etc/init/ 目录。系统启动时会自动加载这个目录下的所有 rc 文件。

此时目录结构是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models/hidl$ tree tvserver/
tvserver/
└── 1.0
├── default
│ ├── Android.bp
│ ├── main.cpp
│ ├── TVServer.cpp
│ └── TVServer.h
│ └── device.skytoby.pure.tvserver@1.0-service.rc
└── ITVServer.hal

2 directories, 6 files
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models/hidl$

4.6 自动生成 hal 接口的 Android.bp

这里需要用到 update-makefiles.sh 这个脚本,我们可以从 $(TOP)/hardware/interfaces 里面 copy 一份过来放到 device/skytoby/pure/models/hidl 目录,稍作修改即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# Script to update Android make-files for HAL and VTS modules.

set -e

if [ -z "$ANDROID_BUILD_TOP" ]; then
echo "Missing ANDROID_BUILD_TOP env variable. Run 'lunch' first."
exit 1
fi

source $ANDROID_BUILD_TOP/system/tools/hidl/update-makefiles-helper.sh

do_makefiles_update \
"device.skytoby.pure:device/skytoby/pure/models/hidl"

然后在 Android 根目录执行

1
./device/skytoby/pure/models/hidl/update-makefiles.sh

这个命令生成了 device/skytoby/pure/models/hidl/tvserver/1.0/Android.bp 这个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// This file is autogenerated by hidl-gen -Landroidbp.

hidl_interface {
name: "device.skytoby.pure.tvserver@1.0",
root: "device.skytoby.pure",
product_specific: true,
srcs: [
"ITVServer.hal",
],
interfaces: [
"android.hidl.base@1.0",
],
gen_java: true,
}

我们需要在这个文件最前面添加以下配置才行,不然会编译不过

1
2
3
4
hidl_package_root {
name: "device.skytoby.pure",
path: "device/skytoby/pure/models/hidl",
}

目前目录结构是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models/hidl$ tree 
.
├── tvserver
│ └── 1.0
│ ├── Android.bp
│ ├── default
│ │ ├── Android.bp
│ │ ├── main.cpp
│ │ ├── TVServer.cpp
│ │ └── TVServer.h
│ │ └── device.skytoby.pure.tvserver@1.0-service.rc
│ └── ITVServer.hal
└── update-makefiles.sh

3 directories, 9 files
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models/hidl$

4.7 更新 current.txt

current.txt 记录了所有 hal 接口的 hash 值,接口有变化时,同时需要更新 current.txt 中的 hash 值 在 interfaces 目录下新建 current.txt 文件,随便写个 hash 值

1
123456 device.skytoby.pure.tvserver@1.0::ITVServer

再执行一遍 update-makefiles.sh,这个时候就会发现提示 hash 值不正确了,同时会给出正确的 hash 值,我们把正确的 hash 值替换到 current.txt 即可。

4.8 模块编译

1
mmm device/skytoby/pure/models/hidl/tvserver/1.0

编译完成后,可以发现 $(TARGET_OUT)/vendor/bin/hw 目录下生成了我们的服务 device.skytoby.pure.tvserver@1.0-service

4.9 更新 manifest.xml

复制 device/generic/goldfish/manifest.xml 到 device/skytoby/pure/manifest.xml manifest.xml 中加上

1
2
3
4
5
6
7
8
9
<hal format="hidl">
<name>device.skytoby.pure.tvserver</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>ITVServer</name>
<instance>default</instance>
</interface>
</hal>

pure/product_copy_files.mk 中加上:

1
2
PRODUCT_COPY_FILES += \
device/skytoby/pure/manifest.xml:vendor/manifest.xml

4.10 添加到 PRODUCT_PACKAGES

修改 device.mk 增加

1
2
PRODUCT_PACKAGES += \
device.skytoby.pure.tvserver@1.0-service

4.11 添加 selinux 规则

系统原生的 selinux 规则在 system/sepolicy 目录下, 我们可以直接修改里面的规则,但不推荐这么做,最好是在 device 目录下添加厂商自己的规则。 在 BoardConfig.mk 里面添加:

1
BOARD_SEPOLICY_DIRS += device/skytoby/pure/sepolicy

然后我们就可以在 pure/sepolicy 目录下添加我们的 selinux 规则了。

新增 tvserver.te

1
2
3
4
5
6
7
8
9
type tvserver, domain;

type tvserver_exec, exec_type, vendor_file_type, file_type;
hwbinder_use(tvserver);
init_daemon_domain(tvserver)
add_hwservice(tvserver, tvserver_hwservice)

allow tvserver hwservicemanager_prop:file {map read open getattr};
allow tvserver system_file:dir {read open getattr search};

新增 file_contexts

1
/vendor/bin/hw/device.skytoby.pure.tvserver@1.0-service u:object_r:tvserver_exec:s0

新增 hwservice.te

1
type tvserver_hwservice, hwservice_manager_type;

新增 hwservice_contexts

然后去编译整个系统, 运行,嘿,我们的服务跑起来了:

4.12 编写 client 调用服务

在 1.0 目录下增加 test 目录,新建 TVServerTest.cpp 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <device/skytoby/pure/tvserver/1.0/ITVServer.h>
#include <hidl/Status.h>
#include <utils/misc.h>
#include <hidl/HidlSupport.h>
#include <stdio.h>
#include <string>

using namespace std;

using ::android::hardware::hidl_string;
using ::android::sp;
using device::skytoby::pure::tvserver::V1_0::ITVServer;

int main(){
android::sp<ITVServer> service = ITVServer::getService();
if (service == nullptr){
printf("Failed to get service\n");
return -1;
}
service->hello("skytoby", [&](hidl_string result){
printf("%s\n", result.c_str());
});

return 0;
}

新建 Android.bp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cc_binary {
name: "TVServerTest",
vendor: true,
srcs: ["TVServerTest.cpp"],

shared_libs: [
"liblog",
"libhardware",
"libhidlbase",
"libhidltransport",
"libutils",
"device.skytoby.pure.tvserver@1.0",
],

}

在 device.mk 中加上

1
2
PRODUCT_PACKAGES += \
TVServerTest

然后整编译系统,运行:

1
2
3
pure:/ # TVServerTest                              
hello skytoby from TVServer
pure:/ #

成功调用了 hidl 服务。

最终代码目录结构是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models/hidl$ tree
.
├── current.txt
├── tvserver
│ └── 1.0
│ ├── Android.bp
│ ├── default
│ │ ├── Android.bp
│ │ ├── device.skytoby.pure.tvserver@1.0-service.rc
│ │ ├── main.cpp
│ │ ├── TVServer.cpp
│ │ └── TVServer.h
│ ├── ITVServer.hal
│ └── test
│ ├── Android.bp
│ └── TVServerTest.cpp
└── update-makefiles.sh

4 directories, 11 files
skytoby@skytoby-pc:~/source/android-10/device/skytoby/pure/models/hidl$

经过了这么多的步骤终于完成了一个 hidl 的 hello world。 以后我们只要在这个框架上慢慢添加功能就行了。

网上大部分文章会忽略掉 current.txt, manifest.xml, selinux 及在 device/product 目录下添加模块的这些问题,导致自己跟着那些文章实现的时候,总是遇到各种问题,希望这篇记录能够帮助到想创建 hidl 服务的同学。

4.13 增加hidl服务回调

上一小节我们已经完整的跑起来了一个 hidl 的 hello demo,这个 demo 虽然非常简单,但已经包含了 hidl 的大部分知识点了,我们只要在这个框架的基础上不断的扩展功能即可。 上一小节的demo 调用是单向的,即只能由 client 调用 service 的功能,但实际项目中我们是经常需要在 service 里面通知 client 某个事件发生了,需要 client 进行处理。也就是双向调用,或者称为 callback。下面我们在上一小节的基础上增加一个回调功能。

4.13.1 增加回调 hal 接口

在 tvserver/1.0 目录下增加 ITVServerListener.hal 文件

1
2
3
4
5
package device.skytoby.pure.tvserver@1.0;

interface ITVServerListener {
oneway onMessage(string message);
};

4.13.2 ITVServer.hal 增加回调注册接口

1
2
3
4
5
6
7
package device.skytoby.pure.tvserver@1.0;
import ITVServerListener;
interface ITVServer {
hello(string name) generates (string result);
registerListener(uint32_t pid, ITVServerListener listener);
unregisterListener(uint32_t pid);
};

4.13.3 更新 current.txt

current.txt 中增加 ITVServerListener 接口的 hash 值,先随便写一个,然后执行

1
./device/skytoby/pure/models/hidl/update-makefiles.sh

会提示 hash 值不对,更新 current.txt 即可, 两个 hal 接口的 hash 值都更新之后,再执行 update-makefiles.sh

4.13.4 实现新增的 hal 接口

TVServer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...

#include <device/skytoby/pure/tvserver/1.0/ITVServerListener.h>
#include <map>
using namespace std;

...

class TVServer : public ITVServer {
public:
Return<void> hello(const hidl_string& name, hello_cb _hidl_cb) override;
Return<void> registerListener(const uint32_t pid, const sp<ITVServerListener>& listener) override;
Return<void> unregisterListener(const uint32_t pid) override;
private:
map<uint32_t, sp<ITVServerListener> > mListeners;
};

...

增加了 registerListener, unregisterListener 两个方法, 这两个方法的参数类型可以根据 hal 文件中声明的参数类型来推断。 hidl 与 c++ 的数据类型转换可以按下表来转换一下,给所有参数都加上 const 数据类型

hal 类型的参数,则使用 const sp& listener 这种智能指针的形式。 当然我们也可以使用 hidl-gen 来自动生成代码。参考上一小节的:根据 .hal 自动生成 cpp 实现。

TVServer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <android/log.h>
#define LOG_TAG "TVServer"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

...

Return<void> TVServer::hello(const hidl_string& name, hello_cb _hidl_cb) {
char buf[100];
::memset(buf, 0, 100);
::snprintf(buf, 100, "hello %s from TVServer", name.c_str());
hidl_string result(buf);
_hidl_cb(result);

LOGD("listener size = %d", mListeners.size());
map<uint32_t, sp<ITVServerListener> >::iterator it;
for (it = mListeners.begin(); it != mListeners.end(); ++it) {
sp<ITVServerListener> listener = it->second;
listener->onMessage("message from server");
}
return Void();
}

Return<void> TVServer::registerListener(const uint32_t pid, const sp<ITVServerListener>& listener) {
mListeners[pid] = listener;
LOGD("registerListener %d, %x", pid, listener.get());
return Void();
}

Return<void> TVServer::unregisterListener(const uint32_t pid) {
mListeners.erase(pid);
LOGD("unregisterListener %d", pid);
return Void();
}

4.13.5 更新 manifest.xml

增加一个interface节点就行

1
2
3
4
5
6
7
8
9
10
11
12
13
<hal format="hidl">
<name>device.skytoby.pure.tvserver</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>ITVServer</name>
<instance>default</instance>
</interface>
<interface>
<name>ITVServerListener</name>
<instance>default</instance>
</interface>
</hal>

4.13.6 更新 client 

TVServerTest.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <device/skytoby/pure/tvserver/1.0/ITVServer.h>
#include <device/skytoby/pure/tvserver/1.0/ITVServerListener.h>
#include <hidl/Status.h>
#include <utils/misc.h>
#include <hidl/HidlSupport.h>
#include <stdio.h>
#include <string>

using namespace std;

using ::android::hardware::hidl_string;
using ::android::sp;
using device::skytoby::pure::tvserver::V1_0::ITVServer;
using device::skytoby::pure::tvserver::V1_0::ITVServerListener;

class TVServerListener : public ITVServerListener {
public:
android::hardware::Return<void> onMessage(const hidl_string& message) override {
printf("onMessage:%s\n", message.c_str());
return android::hardware::Return<void>();
}
};

int main(){

sp<ITVServerListener> listener = new TVServerListener();

android::sp<ITVServer> service = ITVServer::getService();
if (service == nullptr){
printf("Failed to get service\n");
return -1;
}

service->registerListener(getpid(), listener);

service->hello("skytoby", [&](hidl_string result){
printf("%s\n", result.c_str());
});
sleep(1);

service->unregisterListener(getpid());
return 0;
}

4.13.7 编译验证

整编系统,执行

1
2
3
4
5
6
7
8
9
10
11
pure:/ # TVServerTest 
hello skytoby from TVServer
onMessage:message from server
pure:/ # logcat -s TVServer
--------- beginning of main
--------- beginning of system
01-18 13:21:27.414 1665 1903 D TVServer: registerListener 4466, b0443000
01-18 13:21:27.414 1665 1903 D TVServer: listener size = 1
01-18 13:21:28.415 1665 1903 D TVServer: unregisterListener 4466
^C
130|pure:/ #

可以看到收到了 onMessage 回调。

参考资料

http://qiushao.net/categories/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/